﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using Microsoft.Win32;

namespace ApiScanner.Core
{
    internal static class EventFinder
    {
        /// <summary>
        /// Finds event registrations at given server and location
        /// </summary>
        /// <param name="serverHostName"></param>
        /// <param name="mailbox">Either SMTP address of mailbox ("lauren@lavoie.com"), 
        /// SystemMailbox GUID ("{90120000-001A-0409-0000-0000000FF1CE}@office.com") or empty if Public Folders</param>
        /// <param name="mailboxType">MailboxType</param>
        /// <returns>Array of StoreEvent type if present, 'null' if none</returns>
        public static IEnumerable<StoreEvent> GetEvents(
            string serverHostName,
            string mailbox,
            MailboxType mailboxType,
            bool useSSL)
        {
            IEnumerable<StoreEvent> events;
            if (mailboxType == MailboxType.PublicFolder)
            {
                events = FindEventsInFolder(new Uri(mailbox), true, useSSL);
            }
            else
            {
                var path = DavSearchHelper.BuildPath(serverHostName, mailbox, mailboxType, useSSL, null);
                events = FindEventsInFolder(path, false, useSSL);
            }
            return events;
        }

        /// <summary>
        /// Takes URI and then returns StoreEvents in subtree
        /// </summary>
        /// <param name="path"></param>
        /// <param name="isPublicFolders"></param>
        /// <returns></returns>
        private static IEnumerable<StoreEvent> FindEventsInFolder(Uri path, bool isPublicFolders, bool useSSL)
        {
            // Create the body, shallow for Public Folders, deep for mailboxes
            var searchBody =
                "<?xml version=\"1.0\"?>" +
                "<g:searchrequest xmlns:g=\"DAV:\">" +
                "<g:sql>\r\n" +
                "SELECT \"DAV:contentclass\"," +
                "\"DAV:displayname\"," +
                "\"http://schemas.microsoft.com/exchange/events/Criteria\"," +
                "\"http://schemas.microsoft.com/exchange/events/Enabled\"," +
                "\"http://schemas.microsoft.com/exchange/events/EventMethod\"," +
                "\"http://schemas.microsoft.com/exchange/events/MatchScope\"," +
                "\"http://schemas.microsoft.com/exchange/events/Priority\"," +
                "\"http://schemas.microsoft.com/exchange/events/ScriptUrl\"," +
                "\"http://schemas.microsoft.com/exchange/events/SinkClass\"," +
                "\"http://schemas.microsoft.com/exchange/events/TimerExpiryTime\"," +
                "\"http://schemas.microsoft.com/exchange/events/TimerInterval\"," +
                "\"http://schemas.microsoft.com/exchange/events/TimerStartTime\"\r\n" +
                "FROM Scope('" + (isPublicFolders ? "SHALLOW" : "DEEP") + " TRAVERSAL OF \"\"')\r\n" +
                "WHERE \"DAV:contentclass\" = 'urn:content-class:storeeventreg'\r\n" +
                "</></>";

            var searchResult = DavSearchHelper.DoSearch(path, searchBody);
            return (searchResult == null) ? null : ParseResponseStream(searchResult);
        }

    

        /// <summary>
        /// Returns array of StoreEvents from response
        /// </summary>
        /// <param name="responseStream">XML stream resulting from DAV SEARCH for eventreg items</param>
        /// <returns>Array of StoreEvent objects</returns>
        private static IEnumerable<StoreEvent> ParseResponseStream(Stream responseStream)
        {
            const string davNamespace = "DAV:";
            const string proptagNamespace = "http://schemas.microsoft.com/mapi/proptag/";
            const string eventNamespace = "http://schemas.microsoft.com/exchange/events/";

            var storeEvents = new List<StoreEvent>();

            var reader = XmlReader.Create(responseStream);

            var nsmgr = new XmlNamespaceManager(reader.NameTable);
            nsmgr.AddNamespace("dav", davNamespace);
            nsmgr.AddNamespace("e", proptagNamespace);
            nsmgr.AddNamespace("d", eventNamespace);

            var doc = new XPathDocument(reader);

            foreach (XPathNavigator element in doc.CreateNavigator().Select(
                "//dav:response[dav:propstat/dav:status = 'HTTP/1.1 200 OK']",
                nsmgr))
            {
                var evt = new StoreEvent
                                    {
                                        Path = new Uri(element.SelectSingleNode("dav:href", nsmgr).Value)
                                    };

                // now selectively populate based on optional properties, and don't be too efficient about it, we're in a hurry
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:EventMethod", nsmgr) != null)
                {
                    evt.EventMethod = element.SelectSingleNode("dav:propstat/dav:prop/d:EventMethod", nsmgr).Value;
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:SinkClass", nsmgr) != null)
                {
                    evt.SinkClass = element.SelectSingleNode("dav:propstat/dav:prop/d:SinkClass", nsmgr).Value;
                    evt.EncodedComClassEventInfo = ComClassInfo.GetClassInfo(evt.SinkClass, evt.Path.DnsSafeHost);
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/dav:displayname", nsmgr) != null)
                {
                    evt.Name = element.SelectSingleNode("dav:propstat/dav:prop/dav:displayname", nsmgr).Value;
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:Criteria", nsmgr) != null)
                {
                    evt.Criteria = element.SelectSingleNode("dav:propstat/dav:prop/d:Criteria", nsmgr).Value;
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:Enabled", nsmgr) != null)
                {
                    evt.Enabled =
                        element.SelectSingleNode("dav:propstat/dav:prop/d:Enabled", nsmgr).ValueAsBoolean;
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:MatchScope", nsmgr) != null)
                {
                    evt.MatchScope = element.SelectSingleNode("dav:propstat/dav:prop/d:MatchScope", nsmgr).Value;
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:Priority", nsmgr) != null)
                {
                    evt.Priority = element.SelectSingleNode("dav:propstat/dav:prop/d:Priority", nsmgr).ValueAsLong;
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/e:ScriptUrl", nsmgr) != null)
                {
                    evt.ScriptUrl =
                        new Uri(element.SelectSingleNode("dav:propstat/dav:prop/e:ScriptUrl", nsmgr).Value);
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:TimerExpiryTime", nsmgr) != null)
                {
                    evt.TimerExpiryTime =
                        element.SelectSingleNode("dav:propstat/dav:prop/d:TimerExpiryTime", nsmgr).Value;
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:TimerInterval", nsmgr) != null)
                {
                    evt.TimerInterval =
                        element.SelectSingleNode("dav:propstat/dav:prop/d:TimerInterval", nsmgr).ValueAsInt;
                }
                if (element.SelectSingleNode("dav:propstat/dav:prop/d:TimerStartTime", nsmgr) != null)
                {
                    evt.TimerStartTime =
                        element.SelectSingleNode("dav:propstat/dav:prop/d:TimerStartTime", nsmgr).Value;
                }
                storeEvents.Add(evt);
            }

            return storeEvents;
        }
    }
}